﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization.Formatters;
using SymbolIntegration.core.Expressions.Functions;
using SymbolIntegration.core.Helpers;

namespace SymbolIntegration.core.Expressions
{
    public class MultipliersExpression : Expression
    {
        private ConstantExpression m_Constant = new ConstantExpression(1);
        private List<Expression> m_Multipliers = new List<Expression>();

        public static Expression Mull(Expression left, Expression right)
        {
            var result = new MultipliersExpression();
            result.Mul(left);
            result.Mul(right);
            return result;
        }

        internal static void TryAddNormalizedExpression(List<Expression> addendsList, Expression expression)
        {

            bool isFound = false;
            var multipliersExprRight = expression as MultipliersExpression;
            if (multipliersExprRight == null)
            {
                if (expression.IsNegate)
                {
                    multipliersExprRight = (MultipliersExpression)
                        (expression.SetNegate(false) * new ConstantExpression(-1));
                }
                else
                {
                    multipliersExprRight = (MultipliersExpression)
                        (expression * new ConstantExpression(1));
                }
            }

            for (int i = 0; i < addendsList.Count; i++)
            {
                //Ensure two elements are multipliers
                var multipliersExprInList = addendsList[i] as MultipliersExpression;
                if (multipliersExprInList == null)
                {
                    if (addendsList[i].IsNegate)
                    {
                        multipliersExprInList = (MultipliersExpression)
                            (addendsList[i].SetNegate(false)*new ConstantExpression(-1));
                    }
                    else
                    {
                        multipliersExprInList = (MultipliersExpression)
                            (addendsList[i] * new ConstantExpression(1));
                    }
                }

                if (!IsListsEqual(multipliersExprInList.m_Multipliers, multipliersExprRight.m_Multipliers))
                {
                    continue;
                }

                isFound = true;
                var resultElement = (MultipliersExpression) multipliersExprInList.Clone();
                double resultConstant = multipliersExprInList.m_Constant.Value + multipliersExprRight.m_Constant.Value;
                resultElement.m_Constant = new ConstantExpression(resultConstant);
                if (MathHelper.AlmostEqual(resultConstant, 0))
                {
                    addendsList[i] = new ConstantExpression(0);
                    break;
                }

                if (MathHelper.AlmostEqual(resultConstant, 1) && resultElement.m_Multipliers.Count == 1)
                {
                    addendsList[i] = resultElement.m_Multipliers.First();
                    break;
                }

                addendsList[i] = resultElement;
                break;
            }

            if (!isFound)
            {
                addendsList.Add(expression);
            }
        }

        internal static MultipliersExpression ParseItself(string expression, int position, int size)
        {
            int oldPosition = position;
            int oldSize = size;
            ParseUtils.TrimScopes(expression, ref position, ref size);
            var result = new MultipliersExpression();
            if (size == 0)
            {
                throw new ParseException("Expression is scopes or spaces. Need addend", expression, oldPosition, oldSize, 0);
            }

            int startExprPosition = position;
            for (int i = position; i < size + position; i++)
            {
                if (expression[i] == '(')
                {
                    i = ParseUtils.FindCloseScopePosition(expression, i, size + position - i);
                }
                if (expression[i] == '*')
                {
                    Expression multExpression = ParseUtils.Parse(expression, startExprPosition, i - startExprPosition);
                    result.Mul(multExpression);
                    startExprPosition = i + 1;
                }
            }
            Expression lastMultExpression = ParseUtils.Parse(expression, startExprPosition, position + size - startExprPosition);
            result.Mul(lastMultExpression);

            return result;
        }

        protected MultipliersExpression()
        {
            
        }

        public override Expression Differentiate(string variable)
        {
            Expression result;
            do
            {
                if (!IsDependsFrom(variable))
                {
                    result = new ConstantExpression(0);
                    break;
                }

                result = new ConstantExpression(0);
                for (int i = 0; i < m_Multipliers.Count; i++)
                {
                    var multipliers = new MultipliersExpression();
                    for (int j = 0; j < m_Multipliers.Count; j++)
                    {
                        multipliers.Mul(i == j ? m_Multipliers[j].Differentiate(variable) : m_Multipliers[j]);
                    }
                    multipliers.MulConstant(m_Constant);
                    result = result + multipliers;
                }
            } 
            while (false);

            result.InvertNegate(IsNegate);
            return result;
        }

        public override Expression Normalize()
        {
            Expression result;
            do
            {
                var simplifyMultipliers = new MultipliersExpression
                {
                    m_Constant = (ConstantExpression) m_Constant.InvertNegate(IsNegate).Normalize()
                };

                foreach (var multiplier in m_Multipliers)
                {
                    simplifyMultipliers.Mul(multiplier.Normalize());
                }

                var simply = simplifyMultipliers.TrySimpifyByConstant();
                if (simply != null)
                {
                    result = simply;
                    break;
                }

                var multsList = simplifyMultipliers.m_Multipliers.Select(expression => expression).ToList();
                multsList.Add(simplifyMultipliers.m_Constant);
                multsList = SimplifyTrigonimetric(multsList);
                simplifyMultipliers = new MultipliersExpression();
                foreach (var multiplier in multsList)
                {
                    simplifyMultipliers.Mul(multiplier);
                }

                if (simplifyMultipliers.m_Multipliers.Any(expression => expression.Type == ExpressionType.Addends))
                {
                    var newMultsList = simplifyMultipliers.m_Multipliers.Select(expression => expression).ToList();
                    newMultsList.Add(simplifyMultipliers.m_Constant);
                    var adendsDistruted = AddendsExpression.MulDistributed(newMultsList);
                    result = adendsDistruted.Normalize();
                    break;
                }

                var mergedMultList = PowerExpression.MergeMul(multsList);
                mergedMultList = mergedMultList.Select(expression => expression.Normalize()).ToList();
                var multipliersResult = new MultipliersExpression();
                foreach (var expression in mergedMultList)
                {
                    multipliersResult.Mul(expression);
                }
                result = multipliersResult.TrySimpifyByConstant() ?? multipliersResult;
            } 
            while (false);

            return result;
        }

        private List<Expression> SimplifyTrigonimetric(List<Expression> multsList)
        {
            List<Expression> result;
            do
            {
                if (multsList
                    .Count(func => func is CosFunction || func is SinFunction) < 2)
                {
                    result = multsList;
                    break;
                }
                Expression firstSinOrCosExpression = multsList.First(expression =>
                {
                    var funct = expression as FuncExpression;
                    if (funct == null)
                        return false;
                    if (funct.FunctionType == FunctionType.Cos || funct.FunctionType == FunctionType.Sin)
                    {
                        return true;
                    }
                    return false;
                });
                multsList.Remove(firstSinOrCosExpression);

                Expression secondSinOrCosExpression = multsList.First(expression =>
                {
                    var funct = expression as FuncExpression;
                    if (funct == null)
                        return false;
                    if (funct.FunctionType == FunctionType.Cos || funct.FunctionType == FunctionType.Sin)
                    {
                        return true;
                    }
                    return false;
                });
                multsList.Remove(secondSinOrCosExpression);

                var firstCos = firstSinOrCosExpression as CosFunction;
                var firstSin = firstSinOrCosExpression as SinFunction;
                var secondCos = secondSinOrCosExpression as CosFunction;
                var secondSin = secondSinOrCosExpression as SinFunction;

                var multTrigonometric = MulTriginometric(firstCos, secondCos);
                if (multTrigonometric == null)
                {
                    multTrigonometric = MulTriginometric(firstSin, secondSin);
                }
                if (multTrigonometric == null)
                {
                    multTrigonometric = MulTriginometric(secondSin, firstCos);
                }
                if (multTrigonometric == null)
                {
                    multTrigonometric = MulTriginometric(firstSin, secondCos);
                }
                multsList.Add(multTrigonometric);
                result = multsList;
            } while (false);

            return result;
        }

        private Expression MulTriginometric(SinFunction sin, CosFunction cos)
        {
            Expression result;
            if (sin == null || cos == null)
            {
                result = null;
            }
            else
            {
                result = new ConstantExpression(0.5) * (new SinFunction(sin.Argument + cos.Argument)
                                                          + new SinFunction(sin.Argument - cos.Argument));    
            }
            return result;
        }

        private Expression MulTriginometric(SinFunction sin, SinFunction sin2)
        {
            Expression result;
            if (sin == null || sin2 == null)
            {
                result = null;
            }
            else
            {
                result = new ConstantExpression(0.5)*(new CosFunction(sin.Argument - sin2.Argument)
                                                      - new CosFunction(sin.Argument + sin2.Argument));
            }
            return result;
        }

        private Expression MulTriginometric(CosFunction cos, CosFunction cos2)
        {
            Expression result;
            if (cos2 == null || cos == null)
            {
                result = null;
            }
            else
            {
                result = new ConstantExpression(0.5)*(new CosFunction(cos.Argument + cos2.Argument)
                                                          + new CosFunction(cos.Argument - cos2.Argument));
            }
            return result;
        }

        private Expression TrySimpifyByConstant()
        {
            Expression result = null;
            do
            {
                if (MathHelper.AlmostEqual(m_Constant.Value, 0))
                {
                    result = new ConstantExpression(0);
                    break;
                }
                if (m_Multipliers.Count == 0)
                {
                    result = m_Constant.InvertNegate(IsNegate).Normalize();
                    break;
                }

                if (MathHelper.AlmostEqual(m_Constant.Value, 1) && m_Multipliers.Count == 1)
                {
                    result = m_Multipliers.First().InvertNegate(IsNegate).Normalize();
                    break;
                }

                if (MathHelper.AlmostEqual(m_Constant.Value, -1) && m_Multipliers.Count == 1)
                {
                    result = m_Multipliers.First().InvertNegate(!IsNegate).Normalize();
                    break;
                }

            } 
            while (false);

            return result;
        }

        public override bool IsEqual(Expression right)
        {
            bool result = false;
            do
            {
                if (right.IsNegate != IsNegate)
                {
                    break;
                }

                var multipliers = right as MultipliersExpression;
                if (multipliers == null)
                {
                    break;
                }

                if (!IsListsEqual(m_Multipliers, multipliers.m_Multipliers))
                {
                    break;
                }

                if (!m_Constant.IsEqual(multipliers.m_Constant))
                {
                    break;
                }

                result = true;
            } 
            while (false);

            return result;
        }

        public override Polinom FormatToPolinomAsDependentFromVariable(string variable)
        {
            Polinom polinom;
            do
            {
                polinom = new Polinom(variable);
                polinom.SetConstantAt(0, new ConstantExpression(1));
                foreach (var multiplier in m_Multipliers)
                {
                    var mulPolinom = multiplier.FormatToPolinom(variable);

                    if (mulPolinom == null)
                    {
                        polinom = null;
                        break;
                    }
                    polinom.Mul(mulPolinom);
                }
                if (polinom == null)
                {
                    break;
                }

                polinom.Mul(m_Constant.FormatToPolinom(variable));
            } 
            while (false);
            return polinom;
        }

        public override Expression Clone()
        {
            var multiplier = new MultipliersExpression
            {
                m_Multipliers = m_Multipliers.Select(expression => expression).ToList(),
                m_Constant = m_Constant
            };
            InnerCopy(multiplier);
            return multiplier;
        }

        public override Expression Substitute(Dictionary<string, Expression> varValues)
        {
            var clone = new MultipliersExpression();
            InnerCopy(clone);
            foreach (var expression in m_Multipliers)
            {
                clone.Mul(expression.Substitute(varValues));
            }
            clone.MulConstant(m_Constant);
            return clone;
        }

        public override ExpressionType Type
        {
            get { return ExpressionType.Multipliers; }
        }

        public List<Expression> Multipliers { get { return m_Multipliers; } }
        public Expression Constant { get { return m_Constant; } }

        public override string ToString()
        {
            string result = "";
            do
            {
                if (m_Multipliers.Count == 0)
                {
                    result = m_Constant.ToString();
                    break;
                }

                if (!MathHelper.AlmostEqual(m_Constant.Value, 1))
                {
                    result = m_Constant.ToString() + '*';
                }

                bool isFirst = true;
                foreach (var expression in m_Multipliers)
                {
                    if (!isFirst)
                    {
                        result += "*";
                    }

                    if (expression.Type == ExpressionType.Addends)
                    {
                        result += "(" + expression + ")";
                    }
                    else
                    {
                        result += expression;
                    }
                    isFirst = false;
                }
            
            }
            while (false);
            if (IsNegate)
            {
                result = "-(" + result + ")";
            }
            return result;
        }

        protected internal override void RebuildDependancyCache()
        {
            m_Cache.Varibles.Clear();
            foreach (var expression in m_Multipliers)
            {
                if (!expression.m_Cache.IsValid)
                {
                    expression.RebuildDependancyCache();
                }
                m_Cache.Varibles.UnionWith(expression.m_Cache.Varibles);
            }

            m_Cache.IsValid = true;
        }

        protected internal override void FillExpressionStatistic(ExpressionStatistic statistic, string integrateVariable)
        {
            foreach (var multiplier in m_Multipliers)
            {
                multiplier.FillExpressionStatistic(statistic, integrateVariable);
            }
        }

        protected internal override Expression IntegrateAsDependentFromVar(string variable, IIntegrator integrator)
        {
            Expression result;
            do
            {
                if (m_Multipliers.Count == 1)
                {
                    result = m_Multipliers.First().Integrate(variable, integrator) * m_Constant;
                    break;
                }

                var polinom = FormatToPolinom(variable);
                if (polinom != null)
                {
                    result = polinom.Integrate();
                    break;
                }

                var independentMultipliers = new MultipliersExpression();
                m_Multipliers.Where(expression => !expression.IsDependsFrom(variable)).Foreach(independentMultipliers.Mul);
                independentMultipliers.MulConstant(m_Constant);

                var dependentMultipliers = m_Multipliers.Where(expression =>
                    expression.IsDependsFrom(variable)).ToList();
                result = dependentMultipliers.Count == 1 ? 
                    dependentMultipliers.First().Integrate(variable, integrator) : 
                    integrator.TryIntegrateMultipliers(dependentMultipliers, variable);

                result = result*independentMultipliers;
            } 
            while (false);

            return result;
        }

        protected void Mul(Expression expression)
        {
            do
            {
                var constExpr = expression as ConstantExpression;
                if (constExpr != null)
                {
                    MulConstant(constExpr);
                    break;
                }

                var sameTypeExpr = expression as MultipliersExpression;
                if (sameTypeExpr != null)
                {
                    m_Multipliers.AddRange(sameTypeExpr.m_Multipliers);
                    MulConstant(sameTypeExpr.m_Constant);
                }
                else
                {
                    m_Multipliers.Add(expression);
                }
            } 
            while (false);
        }

        protected void MulConstant(ConstantExpression expression)
        {
            double leftVal = (expression.IsNegate) ? -expression.Value : expression.Value;
            double rightVal = m_Constant.Value;
            m_Constant = new ConstantExpression(leftVal * rightVal);
        }
    }
}